﻿using ClumsyWordsPortableLibrary.Model;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Data.Json;
using Windows.Storage;

namespace ClumsyWords.DataModel
{
    /// <summary>
    /// Base class for <see cref="DictionaryDataItem"/> and <see cref="DictionaryDataGroup"/> that
    /// defines properties common to both.
    /// </summary>
    [Windows.Foundation.Metadata.WebHostHidden]
    public abstract class DictionaryDataCommon : ClumsyWords.Common.BindableBase
    {
        public DictionaryDataCommon(String key)
            :this(key, String.Empty, String.Empty)
        {
        }

        public DictionaryDataCommon(String key, String title, String description)
        {
            this._Id = new Guid();
            this._key = key;
            this._title = title;
            this._description = description;
        }

        private Guid _Id;
        public Guid Id
        {
            get { return this._Id; }
            set { this.SetProperty(ref this._Id, value); }
        }

        private string _key;
        public string Key 
        {
            get { return this._key; }
            set { this.SetProperty(ref this._key, value); }
        }

        private string _title = string.Empty;
        public string Title
        {
            get { return this._title; }
            set { this.SetProperty(ref this._title, value); }
        }

        private string _description = string.Empty;
        public string Description
        {
            get { return this._description; }
            set { this.SetProperty(ref this._description, value); }
        }

        public override string ToString()
        {
            return this.Title;
        }
    }

    public class DictionaryDataItem : DictionaryDataCommon
    {
        public DictionaryDataItem()
            : this(String.Empty, String.Empty, new ObservableCollection<TermProperties>())
        {
        }

        public DictionaryDataItem(string key, string term, ObservableCollection<TermProperties> items)
            :base(key)
        {
            this._term = term;
            this._items = items;
            this.Items.CollectionChanged += ItemsCollectionChanged;
        }

        void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    if (e.NewStartingIndex < 12)
                    {
                        TopItems.Insert(e.NewStartingIndex, Items[e.NewStartingIndex]);
                        if (TopItems.Count > 12)
                        {
                            TopItems.RemoveAt(12);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    if (e.OldStartingIndex < 12 && e.NewStartingIndex < 12)
                    {
                        TopItems.Move(e.OldStartingIndex, e.NewStartingIndex);
                    }
                    else if (e.OldStartingIndex < 12)
                    {
                        TopItems.RemoveAt(e.OldStartingIndex);
                        TopItems.Add(Items[11]);
                    }
                    else if (e.NewStartingIndex < 12)
                    {
                        TopItems.Insert(e.NewStartingIndex, Items[e.NewStartingIndex]);
                        TopItems.RemoveAt(12);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    if (e.OldStartingIndex < 12)
                    {
                        TopItems.RemoveAt(e.OldStartingIndex);
                        if (Items.Count >= 12)
                        {
                            TopItems.Add(Items[11]);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    if (e.OldStartingIndex < 12)
                    {
                        TopItems[e.OldStartingIndex] = Items[e.OldStartingIndex];
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    TopItems.Clear();
                    while (TopItems.Count < Items.Count && TopItems.Count < 12)
                    {
                        TopItems.Add(Items[TopItems.Count]);
                    }
                    break;
            }
        }

        private string _term;
        public string Term
        {
            get { return this._term; }
            set { this.SetProperty(ref this._term, value); }
        }

        private ObservableCollection<TermProperties> _items;
        public ObservableCollection<TermProperties> Items 
        {
            get { return this._items; }
            set { this.SetProperty(ref this._items, value); } 
        }
        public List<Group<PartOfSpeech, TermProperties>> GroupedItems
        {
            get { return Group<PartOfSpeech, TermProperties>.GetGroups(this._items.ToList(), p => p.PartOfSpeech).ToList(); }
            private set { this.SetProperty(ref this._items, Group<PartOfSpeech, TermProperties>.Ungroup(value) as ObservableCollection<TermProperties>); }
        }

        private ObservableCollection<TermProperties> _topItems = new ObservableCollection<TermProperties>();
        public ObservableCollection<TermProperties> TopItems
        {
            get { return this._topItems; }
        }


    }

    public class DictionaryDataGroup : DictionaryDataCommon
    {
        public DictionaryDataGroup()
            : base(String.Empty, String.Empty, String.Empty)
        {
        }

        public DictionaryDataGroup(String key, String title, String description)
            : base(key, title, description)
        {
            Items.CollectionChanged += ItemsCollectionChanged;
        }

        private void ItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            // Provides a subset of the full items collection to bind to from a GroupedItemsPage
            // for two reasons: GridView will not virtualize large items collections, and it
            // improves the user experience when browsing through groups with large numbers of
            // items.
            //
            // A maximum of 12 items are displayed because it results in filled grid columns
            // whether there are 1, 2, 3, 4, or 6 rows displayed

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    if (e.NewStartingIndex < 12)
                    {
                        TopItems.Insert(e.NewStartingIndex, Items[e.NewStartingIndex]);
                        if (TopItems.Count > 12)
                        {
                            TopItems.RemoveAt(12);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    if (e.OldStartingIndex < 12 && e.NewStartingIndex < 12)
                    {
                        TopItems.Move(e.OldStartingIndex, e.NewStartingIndex);
                    }
                    else if (e.OldStartingIndex < 12)
                    {
                        TopItems.RemoveAt(e.OldStartingIndex);
                        TopItems.Add(Items[11]);
                    }
                    else if (e.NewStartingIndex < 12)
                    {
                        TopItems.Insert(e.NewStartingIndex, Items[e.NewStartingIndex]);
                        TopItems.RemoveAt(12);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    if (e.OldStartingIndex < 12)
                    {
                        TopItems.RemoveAt(e.OldStartingIndex);
                        if (Items.Count >= 12)
                        {
                            TopItems.Add(Items[11]);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    if (e.OldStartingIndex < 12)
                    {
                        TopItems[e.OldStartingIndex] = Items[e.OldStartingIndex];
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    TopItems.Clear();
                    while (TopItems.Count < Items.Count && TopItems.Count < 12)
                    {
                        TopItems.Add(Items[TopItems.Count]);
                    }
                    break;
            }
        }

        private ObservableCollection<DictionaryDataItem> _items = new ObservableCollection<DictionaryDataItem>();
        public ObservableCollection<DictionaryDataItem> Items
        {
            get { return this._items; }
        }

        private ObservableCollection<DictionaryDataItem> _topItem = new ObservableCollection<DictionaryDataItem>();
        public ObservableCollection<DictionaryDataItem> TopItems
        {
            get { return this._topItem; }
        }
    }

    public sealed class DictionaryDataSource
    {
        private static DictionaryDataSource _definitionDataSource = new DictionaryDataSource();

        //  This property is used for groups such as "Favourites" and "Recent" which consist of DictionaryDataItem(s) and
        //  are considered the highest level of data structure in this model.
        //  Each item of a TopLevelGroup is a 
        private ObservableCollection<DictionaryDataGroup> _topLevelGroups = new ObservableCollection<DictionaryDataGroup>();
        public ObservableCollection<DictionaryDataGroup> TopLevelGroups
        {
            get { return this._topLevelGroups; }
        }

        //  This property is used for groups which contain grouped term properties only - e.g "Noun", "Verb"
        private ObservableCollection<DictionaryDataItem> _lowLevelGroups = new ObservableCollection<DictionaryDataItem>();
        public ObservableCollection<DictionaryDataItem> LowLevelGroups
        {
            get { return this._lowLevelGroups; }
        }

        public static IEnumerable<DictionaryDataGroup> GetTopLevelGroups(string key)
        {
            if (!key.Equals("TopLevelGroups")) throw new ArgumentException("Only 'TopLevelGroups' is supported as a collection of top level groups");

            return _definitionDataSource.TopLevelGroups;
        }

        public static DictionaryDataGroup GetTopLevelGroup(string key)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _definitionDataSource.TopLevelGroups.Where((group) => group.Key.Equals(key));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static IEnumerable<DictionaryDataItem> GetLowLevelGroups(string key)
        {
            if (!key.Equals("LowLevelGroups")) throw new ArgumentException("Only 'LowLevelGroups' is supported as a collection of low level groups");

            return _definitionDataSource.LowLevelGroups;
        }

        public static DictionaryDataItem GetLowLevelGroup(string key)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _definitionDataSource.LowLevelGroups.Where((group) => group.Key.Equals(key));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static DictionaryDataItem GetItem(string key)
        {
            // Simple linear search is acceptable for small data sets
            var matches = _definitionDataSource.TopLevelGroups.SelectMany(group => group.Items).Where((item) => item.Key.Equals(key));
            if (matches.Count() == 1) return matches.First();
            return null;
        }

        public static async Task LoadLocalDataAsync(string[] fileNames) 
        {
            foreach (var fileName in fileNames)
            {
                var file = await Package.Current.InstalledLocation.GetFileAsync(fileName);
                var result = await FileIO.ReadTextAsync(file);

                var group = await JsonConvert.DeserializeObjectAsync<DictionaryDataGroup>(result);
                _definitionDataSource.TopLevelGroups.Add(group);
            }
        }

        //Load state
        //public static async Task LoadRemoteDataAsync()
        //{
        //    // Retrieve definition data from somewhere
        //    var client = new HttpClient();
        //    client.MaxResponseContentBufferSize = 1024 * 1024; // Read up to 1 MB of data
        //    var response = await client.GetAsync(new Uri("http://contosorecipes8.blob.core.windows.net/AzureRecipesRP"));
        //    var result = await response.Content.ReadAsStringAsync();

        //    // Parse the JSON definition data
        //    var definitions = JsonArray.Parse(result);

        //    // Convert the JSON objects into RecipeDataItems and RecipeDataGroups
        //    CreateRecipesAndRecipeGroups(definitions);
        //}

        //public static async Task LoadLocalDataAsync()
        //{
        //    // Retrieve definition data from Recipes.txt
        //    var file = await Package.Current.InstalledLocation.GetFileAsync("Data\\Definitions.txt");
        //    var result = await FileIO.ReadTextAsync(file);

        //    // Parse the JSON definition data
        //    var definitions = JsonArray.Parse(result);

        //    // Convert the JSON objects into RecipeDataItems and RecipeDataGroups
        //    CreateRecipesAndRecipeGroups(definitions);
        //}

        //private static void CreateRecipesAndRecipeGroups(JsonArray array)
        //{
        //    foreach (var item in array)
        //    {
        //        var obj = item.GetObject();
        //        DictionaryDataItem termDefinition = new DictionaryDataItem();
        //        DictionaryDataGroup group = null;

        //        foreach (var key in obj.Keys)
        //        {
        //            IJsonValue val;
        //            if (!obj.TryGetValue(key, out val))
        //                continue;

        //            switch (key)
        //            {
        //                case "key":
        //                    termDefinition.Key = val.GetNumber().ToString();
        //                    break;
        //                case "Term":
        //                    termDefinition.Title = val.GetString();
        //                    break;
        //                case "Definition":
        //                    termDefinition.Definition = val.GetString();
        //                    break;
        //                case "Example":
        //                    termDefinition.Example = val.GetString();
        //                    break;
        //                case "Group":
        //                    var termDefinitionGroup = val.GetObject();

        //                    IJsonValue groupKey;
        //                    if (!termDefinitionGroup.TryGetValue("key", out groupKey))
        //                        continue;

        //                    group = _definitionDataSource.TopLevelGroups.FirstOrDefault(c => c.UniqueId.Equals(groupKey.GetString()));

        //                    if (group == null)
        //                        group = CreateRecipeGroup(termDefinitionGroup);

        //                    termDefinition.Group = group;
        //                    break;
        //            }
        //        }

        //        if (group != null)
        //            group.Items.Add(termDefinition);
        //    }
        //}

        //private static DictionaryDataGroup CreateRecipeGroup(JsonObject obj)
        //{
        //    DictionaryDataGroup group = new DictionaryDataGroup();

        //    foreach (var key in obj.Keys)
        //    {
        //        IJsonValue val;
        //        if (!obj.TryGetValue(key, out val))
        //            continue;

        //        switch (key)
        //        {
        //            case "key":
        //                group.UniqueId = val.GetString();
        //                break;
        //            case "title":
        //                group.Title = val.GetString();
        //                break;
        //            case "description":
        //                group.Description = val.GetString();
        //                break;
        //        }
        //    }

        //    _definitionDataSource.TopLevelGroups.Add(group);
        //    return group;
        //}
    }
}
